import json
import time
from decimal import Decimal

from flask_apscheduler import APScheduler
from flask_cors import CORS
from apscheduler.triggers.cron import CronTrigger
from flask_migrate import Migrate
from sqlalchemy import func
from flask_socketio import SocketIO
from flask import Flask, request
from threading import Lock
import config
from models import *
from exts import db, mail
import logging
from utils.SchedulerUtil import SchedulerUtil
from logging.handlers import RotatingFileHandler
from blueprints.base import base_bp
from blueprints.data_analysis import data_analysis_bp
from blueprints.model_use import model_use_bp
from blueprints.task import task_bp
from blueprints.video import video_bp
from blueprints.alert import alert_bp
from blueprints.comment import comment_bp
from script.update_task_script import update_task
from script.new_task_script import new_task
from Common import Common

# 创建Flask应用程序实例
app = Flask(__name__)
socketio = SocketIO(app, path='/socket.io', cors_allowed_origins="*")

CORS(app)
# 绑定配置文件
app.config.from_object(config.DevelopmentConfig)

# 不要打印sql日志
app.config['SQLALCHEMY_ECHO'] = False
# ASPscheduler要求的，不知道为什么
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False

# 数据库迁移的初始化
migrate = Migrate(app, db)
db.init_app(app)

# 初始化邮件功能
mail.init_app(app)

# 注册蓝图
app.register_blueprint(base_bp)
app.register_blueprint(data_analysis_bp)
app.register_blueprint(model_use_bp)
app.register_blueprint(task_bp)
app.register_blueprint(video_bp)
app.register_blueprint(alert_bp)
app.register_blueprint(comment_bp)


scheduler = SchedulerUtil.get_scheduler()
scheduler.init_app(app)

scheduler.add_job(
    func=new_task,
    trigger=CronTrigger(minute='*/' + str(Common.NEW_TASK_TRIGGER_TIME), second=0),
    # trigger=CronTrigger(minute='*', second=0),
    id='new_task',
    replace_existing=True
)
scheduler.add_job(
    func=update_task,
    trigger=CronTrigger(minute='*/' + str(Common.UPDATE_TASK_TRIGGER_TIME), second=0),
    # trigger=CronTrigger(minute='*', second=0),
    id='update_comment',
    replace_existing=True
)
scheduler.start()

# 日志
# flask-apscheduler内置有日志器，为了让内部的日志器打印的内容输出，我这里做了个配置
# 创建日志记录器，指明日志保存路径，每个日志大小，保存日志文件个数上限
file_log_handler = RotatingFileHandler('logs/runserver.log', maxBytes=1024 * 1024 * 100, backupCount=5)
# 创建日志的记录格式,]，日志等级，记录时间，报错位置，行数，日志信息
formatter = logging.Formatter(
    fmt="%(asctime)s - %(levelname)s - %(threadName)s:%(thread)s - %(filename)s - %(funcName)s - %(message)s",
    datefmt='%Y-%m-%d %H:%M:%S %a'
)
# 为刚创建的日志记录器设置日志记录格式
file_log_handler.setFormatter(formatter)
# 为全局日志对象添加日志记录器
logger = logging.getLogger("apscheduler")
logger.addHandler(file_log_handler)
logger.setLevel(logging.INFO)

thread = None
thread_lock = Lock()
connected_sids = set()  # 存放已连接的客户端


@socketio.on('connect')
def on_connect():
    connected_sids.add(request.sid)
    print(f'{request.sid} 已连接')
    global thread
    with thread_lock:
        print(thread)
        if thread is None:
            # 如果socket连接，则开启一个线程，专门给前端发送消息
            thread = socketio.start_background_task(target=check_alert)


@socketio.on('disconnect')
def on_disconnect():
    connected_sids.remove(request.sid)
    print(f'{request.sid} 已断开')


# 自定义 JSON 编码器，用于处理 datetime 对象和 UTF-8 编码的文本
class CustomEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, datetime):
            return o.isoformat()
        elif isinstance(o, Decimal):
            return str(o)  # 将 Decimal 对象转换为字符串
        return super().default(o)


def check_alert():
    while True:
        # 检查是否有满足警报条件的视频
        with app.app_context():
            alerts = db.session.query(Alert).filter_by(is_active=True).all()
            alert_videos = []
            for a in alerts:
                v = db.session.query(MonitoredVideo).filter_by(bvid=a.video_bvid, is_banned=False).first()
                if v:
                    v = v.__dict__
                    v['alter_rate'] = None
                    del v['_sa_instance_state']
                    alert_videos.append(v)
        if len(alert_videos) > 0:
            for video in alert_videos:
                video_json = json.dumps(video, indent=4, cls=CustomEncoder)  # 使用自定义编码器
                socketio.emit('alert', {'message': video_json}, namespace='/')
        time.sleep(Common.ALTER_TIME)


if __name__ == '__main__':
    socketio.run(app, host='127.0.0.1', port=5000, use_reloader=False)
